#include "SVMScaler.h"
#include <cfloat>
#include <cstdlib>
#include <cctype>
#include <cstring>

#include <fstream>

#define SKIP_TARGET(p)\
	while(isspace(*(p))) ++(p);\
	while(!isspace(*(p))) ++(p);

#define SKIP_ELEMENT(p)\
	while(*(p) != ':') ++(p);\
	++(p);\
	while(isspace(*(p))) ++(p);\
	while(*(p) && !isspace(*(p))) ++(p);

#define max(x, y) (((x) > (y)) ? (x) : (y))
#define min(x, y) (((x) < (y)) ? (x) : (y))

SVMScaler::SVMScaler() {
	yMax = -DBL_MAX;
	yMin = DBL_MAX;

	maxIndex = 0;
	minIndex = 1;

	numNonZeros = 0;
	numNonZerosNew = 0;

	lower = -1;
	upper = 1;

	isYScaling = false;
	yLower = -1;
	yUpper = 1;
}

void SVMScaler::initialize(double lower,
		double upper,
		bool isYScaling,
		double yLower,
		double yUpper) {
	this->lower = lower;
	this->upper = upper;
	this->isYScaling = isYScaling;
	this->yLower = yLower;
	this->yUpper = yUpper;
}

void SVMScaler::restore(const char* fileName) {
	restoreMaxMinIndex(fileName);
	restoreXYAndFeatures(fileName);
}

void SVMScaler::restoreMaxMinIndex(const char* fileName) {
	maxIndex = 0;
	minIndex = 1;

	ifstream ifs;
	ifs.open(fileName);
	if (!ifs.is_open()) {
		fprintf(stderr, "can't open file %s\n", fileName);
		return;
	}

	char c = ifs.get();
	string line;
	if (c == 'y') {
		//skip y record
		getline(ifs, line);
		getline(ifs, line);
		getline(ifs, line);
	}
	//skip x record
	getline(ifs, line);
	getline(ifs, line);

	//find max index of attributes 
	int idx;
	while (!ifs.eof()) {
		ifs >> idx;
		maxIndex = max(idx, maxIndex);
		getline(ifs, line);
		if (ifs.eof()) {
			break;
		}
	}
	ifs.close();
}

void SVMScaler::restoreXYAndFeatures(const char* fileName) {
	for (int i = 0; i < maxIndex + 1; i++) {
		featureMax.push_back(-DBL_MAX);
		featureMin.push_back(DBL_MAX);
	}

	int idx, c;
	double fmin, fmax;
	int next_index = minIndex;

	FILE *fp_restore = fopen(fileName, "r");
	if ((c = fgetc(fp_restore)) == 'y') {
		fscanf(fp_restore, "%lf %lf\n", &yLower, &yUpper);
		fscanf(fp_restore, "%lf %lf\n", &yMin, &yMax);
		isYScaling = true;
	} else
		ungetc(c, fp_restore);

	if (fgetc(fp_restore) == 'x') {
		fscanf(fp_restore, "%lf %lf\n", &lower, &upper);
		while (fscanf(fp_restore, "%d %lf %lf\n", &idx, &fmin, &fmax) == 3) {
			for (int i = next_index; i < idx; i++)
				if (featureMin[i] != featureMax[i])
					fprintf(stderr,
						"WARNING: feature index %d was not seen in the scaling factor file %s.\n",
						i, fileName);

			featureMin[idx] = fmin;
			featureMax[idx] = fmax;

			next_index = idx + 1;
		}

		for (int i = next_index; i <= maxIndex; i++)
			if (featureMin[i] != featureMax[i])
				fprintf(stderr,
					"WARNING: feature index %d was not seen in the scaling factor file %s.\n",
					i, fileName);
	}
	fclose(fp_restore);
}

void SVMScaler::scaleData(double y, vector<int> indices, vector<double> values,
		double& scaledY, vector<int>& scaledIndices, vector<double>& scaledValues) {
	scaledY = scaleY(y);
	scaledIndices.clear();
	scaledValues.clear();
	for (int i = 0; i < indices.size(); i++) {
		scaledIndices.push_back(indices[i]);
		scaledValues.push_back(scaleX(indices[i], values[i]));
	}
}

void SVMScaler::scaleData(double& y, vector<int>& indices, vector<double>& values) {
	y = scaleY(y);
	for (int i = 0; i < indices.size(); i++) {
		values[i] = scaleX(indices[i], values[i]);
	}
}

void SVMScaler::scaleFile(const char *outFileName,
		const char *inFileName,
		bool isSaveParameter,
		const char *saveFileName,
		bool isRestoreParameter,
		const char * restoreFileName) {

	const char *save_filename = NULL;
	if (isSaveParameter) {
		save_filename = saveFileName;
	}
	const char *restore_filename = NULL;
	if (isRestoreParameter) {
		restore_filename = restoreFileName;
	}

	if (!(upper > lower) || (isYScaling && !(yUpper > yLower))) {
		fprintf(stderr, "inconsistent lower/upper specification\n");
		return;
	}

	if (restore_filename && save_filename) {
		fprintf(stderr, "cannot use -r and -s simultaneously\n");
		return;
	}

	numNonZeros = 0;
	numNonZerosNew = 0;

	/* assumption: min index of attributes is 1 */

	/* pass 1: find out max index of attributes */
	findMaxMinIndex(inFileName, isRestoreParameter, restore_filename);

	/* pass 2: find out min/max value */
	/* pass 2.5: save/restore feature_min/feature_max */

	if (restore_filename) {
		restoreXYAndFeatures(restore_filename);
	} else {
		findMaxMinValues(inFileName);
	}


	if (isSaveParameter) {
		saveParameters(save_filename);
	}

	/* pass 3: scale */
	FILE *outFile = fopen(outFileName, "w");
	if (outFile == NULL) {
		fprintf(stderr, "can't open file %s\n", save_filename);
	}

	ifstream ifs;
	ifs.open(inFileName);
	if (!ifs.is_open()) {
		fprintf(stderr, "can't open file %s\n", inFileName);
		return;
	}
	string line;
	while (getline(ifs, line)) {
		double target;
		vector<int> indices;
		vector<double> values;
		parseData(line, target, indices, values);
		outputY(outFile, target);
		for (int i = 0; i < indices.size(); i++) {
			outputX(outFile, indices[i], values[i]);
		}

		fprintf(outFile, "\n");
	}
	ifs.close();

	if (numNonZerosNew > numNonZeros)
		fprintf(stderr,
			"WARNING: original #nonzeros %ld\n"
			"         new      #nonzeros %ld\n"
			"Use -l 0 if many original feature values are zeros\n",
			numNonZeros, numNonZerosNew);

}

void SVMScaler::parseData(const string& line,
		double& y,
		vector<int>& indices,
		vector<double>& values) {
	const char *p = line.c_str();
	int index;
	int next_index = 1;
	double value;

	sscanf(p, "%lf", &y);

	SKIP_TARGET(p);

	indices.clear();
	values.clear();
	while (sscanf(p, "%d:%lf", &index, &value) == 2) {
		for (int i = next_index; i < index; i++) {
			indices.push_back(i);
			values.push_back(0);
		}

		indices.push_back(index);
		values.push_back(value);

		SKIP_ELEMENT(p);
		next_index = index + 1;
	}

	for (int i = next_index; i <= maxIndex; i++) {
		indices.push_back(i);
		values.push_back(0);
	}
}

void SVMScaler::findMaxMinIndex(const char* filename,
		bool isRestoreParameter,
		const char * restoreFileName) {
	maxIndex = 0;
	minIndex = 1;
	if (isRestoreParameter) {
		restoreMaxMinIndex(restoreFileName);
	}

	ifstream ifs;
	ifs.open(filename);
	if (!ifs.is_open()) {
		fprintf(stderr, "can't open file %s\n", filename);
		return;
	}

	//find max and min indices in data file
	int index;
	string line;
	while (getline(ifs, line)) {
		const char *p = line.c_str();

		SKIP_TARGET(p);

		while (sscanf(p, "%d:%*f", &index) == 1) {
			maxIndex = max(maxIndex, index);
			minIndex = min(minIndex, index);
			SKIP_ELEMENT(p);
			numNonZeros++;
		}
	}
	ifs.close();

	if (minIndex < 1)
		fprintf(stderr,
			"WARNING: minimal feature index is %d, but indices should start from 1\n", minIndex);

}

void SVMScaler::findMaxMinValues(const char* filename) {
	for (int i = 0; i < maxIndex + 1; i++) {
		featureMax.push_back(-DBL_MAX);
		featureMin.push_back(DBL_MAX);
	}

	ifstream ifs;
	ifs.open(filename);
	if (!ifs.is_open()) {
		fprintf(stderr, "can't open file %s\n", filename);
		return;
	}

	string line;
	while (getline(ifs, line)) {
		const char *p = line.c_str();
		int index;
		int next_index = 1;
		double target;
		double value;

		sscanf(p, "%lf", &target);
		yMax = max(yMax, target);
		yMin = min(yMin, target);

		SKIP_TARGET(p);

		while (sscanf(p, "%d:%lf", &index, &value) == 2) {
			for (int i = next_index; i < index; i++) {
				featureMax[i] = max(featureMax[i], 0);
				featureMin[i] = min(featureMin[i], 0);
			}

			featureMax[index] = max(featureMax[index], value);
			featureMin[index] = min(featureMin[index], value);

			SKIP_ELEMENT(p);
			next_index = index + 1;
		}

		for (int i = next_index; i <= maxIndex; i++) {
			featureMax[i] = max(featureMax[i], 0);
			featureMin[i] = min(featureMin[i], 0);
		}
	}

	ifs.close();
}

void SVMScaler::saveParameters(const char* save_filename) {
	FILE *fp_save = fopen(save_filename, "w");
	if (fp_save == NULL) {
		fprintf(stderr, "can't open file %s\n", save_filename);
		return;
	}
	if (isYScaling) {
		fprintf(fp_save, "y\n");
		fprintf(fp_save, "%.16g %.16g\n", yLower, yUpper);
		fprintf(fp_save, "%.16g %.16g\n", yMin, yMax);
	}
	fprintf(fp_save, "x\n");
	fprintf(fp_save, "%.16g %.16g\n", lower, upper);
	for (int i = 1; i <= maxIndex; i++) {
		if (featureMin[i] != featureMax[i])
			fprintf(fp_save, "%d %.16g %.16g\n", i, featureMin[i], featureMax[i]);
	}

	if (minIndex < 1)
		fprintf(stderr,
			"WARNING: scaling factors with indices smaller than 1 are not stored to the file %s.\n", save_filename);

	fclose(fp_save);
}

double SVMScaler::scaleY(double value) {
	if (isYScaling) {
		if (value == yMin)
			value = yLower;
		else if (value == yMax)
			value = yUpper;
		else value = yLower + (yUpper - yLower) *
			(value - yMin) / (yMax - yMin);
	}

	return value;
}

double SVMScaler::scaleX(int index, double value) {
	/* skip single-valued attribute */
	if (featureMax[index] == featureMin[index])
		return 0;

	if (value == featureMin[index])
		value = lower;
	else if (value == featureMax[index])
		value = upper;
	else
		value = lower + (upper - lower) *
		(value - featureMin[index]) /
		(featureMax[index] - featureMin[index]);

	return value;
}

void SVMScaler::outputY(FILE *f, double value) {
	fprintf(f, "%g ", scaleY(value));
}

void SVMScaler::outputX(FILE *f, int index, double value) {
	value = scaleX(index, value);

	if (value != 0) {
		fprintf(f, "%d:%g ", index, value);
		numNonZerosNew++;
	}
}
